﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace PDX.ContextManagement
{
    /// <summary>
    /// A default <see cref="IPropertyResolver"/>
    /// </summary>
    public class DefaultPropertyResolver : IPropertyResolver
    {

        public IPropertyResolution Resolve(object dataContext, Declaration propertyDeclaration)
        {
            var propertyPath = propertyDeclaration.Path;
            var retval = new DefaultPropertyResolution();
            retval.OriginalDC = dataContext;
            retval.PropertyPath = propertyPath;

            // split a complex path into its parts (e.g. Child.Subchild.Property)
            var parts = propertyPath.Split('.');

            object target = dataContext;
            PropertyInfo prop = null;
            object newTarget = null;

            foreach(var part in parts)
            {
                if (newTarget != null)
                {
                    target = newTarget;
                }

                if (!ResolvePart(target, part, out prop, out newTarget, out target))
                {
                    return null;

                    // TODO Throwing an exception here is too slow at runtime.
                    // But maybe we want to keep this behavior at design-time
                    // to help identify binding errors?
                    //throw new Exception("no public property " + propertyPath +
                    //    " was found for viewmodel " + viewmodel.ToString());
                }
            }

            retval.ResolvedDC = target;
            retval.Property = prop;

            return retval;
        }

        /// <summary>
        /// Resolves a single part of a property path.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="part">The part.</param>
        /// <param name="resolvedProp">The resolved prop.</param>
        /// <param name="propValue">The prop value.</param>
        /// <param name="resolvedTarget">The resolved target.</param>
        /// <returns></returns>
        protected virtual bool ResolvePart(object target, string part,
            out PropertyInfo resolvedProp, out object propValue, out object resolvedTarget)
        {
            if (target != null)
            {
                var prop = target.GetType().GetProperties().FirstOrDefault(p => p.Name == part);
                if (prop != null)
                {
                    propValue = prop.GetValue(target, null);
                    resolvedProp = prop;
                    resolvedTarget = target;
                    return true;
                }
                else
                {
                    var propSources = target.GetType().GetProperties();
                    foreach (var propSource in propSources)
                    {
                        if (!propSource.HasSingleAttribute<PropertySourceAttribute>())
                        {
                            continue;
                        }

                        var potentialTarget = propSource.GetValue(target, null);

                        if (ResolvePart(potentialTarget, part, out resolvedProp, out propValue, out resolvedTarget))
                        {
                            return true;
                        }
                    }
                }
            }

            propValue = null;
            resolvedProp = null;
            resolvedTarget = null;
            return false;
        }
    }
}
